package edu.scau.mis.sys.security.filter;

import edu.scau.mis.core.redis.RedisCache;
import edu.scau.mis.core.utils.StringUtils;
import edu.scau.mis.sys.security.domain.LoginUser;
import edu.scau.mis.sys.security.service.TokenService;
import edu.scau.mis.sys.security.utils.SecurityUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
public class MisAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    RedisCache redisCache;

    @Autowired
    TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //1. 从请求头获取token
        String token = request.getHeader("Authorization");

        //不存在token予以放行，下一个过滤器会继续过滤
        //客户端允许匿名访问，因此直接放行
        // !!!如果不增加判断条件，白名单url会因为token为空报错，从而导致白名单失效!!!
        if (!org.springframework.util.StringUtils.hasText(token) || tokenService.parseToken(token).getSubject().startsWith("user")) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }

        // 2. 解析token获得claims
        Claims claims = tokenService.parseToken(token);

        // 3. 判断token是否过期
        if (tokenService.isTokenExpired(claims)) {
            throw new JwtException("token已过期");
        }

        // 4. 得到subject
        String username = claims.getSubject();  // 获取token是设置的subject是用户名。注意subject可以是LoginUser的任意唯一字段

        // 5. subject与LoginUser关联
        String userKey = username;  // redis缓存需设置唯一值，建议uuid
        LoginUser loginUser = redisCache.getCacheObject(userKey);

        // 如果认证过期
        if(loginUser.getExpireTime() < System.currentTimeMillis()) {
            throw new CredentialsExpiredException("用户缓存有效期已过，请重新登录");
        } else {
            // 更新缓存有效期时间
            loginUser.setLoginTime(System.currentTimeMillis());
            int expireTime = 30 * 60 * 1000; //设置loginUser的缓存过期时间为30分钟。建议与token过期时间相等或者小于token过期时间。
            loginUser.setExpireTime(loginUser.getLoginTime() + expireTime);
            redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        }

        // 6. 如果得到loginUser，使用改loginUser进行用户认证
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }

        // 7. 进入下一个过滤器(即UsernamePasswordAuthenticationFilter)进行用户认证
        filterChain.doFilter(request, response);
    }
}